<template>
  <div class="tutorial-page">
    <div class="tutorial-header">
      <h1>Axios HTTP 请求</h1>
      <p>掌握使用Axios进行HTTP请求和数据交互</p>
    </div>

    <div class="tutorial-content">
      <div class="section">
        <h2>Axios 简介</h2>
        <p>Axios 是一个基于 Promise 的 HTTP 客户端，可以用在浏览器和 node.js 中。它具有以下特性：</p>
        <ul>
          <li>从浏览器中创建 XMLHttpRequests</li>
          <li>从 node.js 创建 http 请求</li>
          <li>支持 Promise API</li>
          <li>拦截请求和响应</li>
          <li>转换请求数据和响应数据</li>
          <li>取消请求</li>
          <li>自动转换 JSON 数据</li>
          <li>客户端支持防御 XSRF</li>
        </ul>
      </div>

      <div class="section">
        <h2>安装和基本配置</h2>
        
        <h3>安装 Axios</h3>
        <pre><code class="language-bash"># 使用 npm
npm install axios

# 使用 yarn
yarn add axios</code></pre>
        
        <h3>基本导入和使用</h3>
        <pre><code class="language-javascript">import axios from 'axios'

// 发送 GET 请求
axios.get('/user?ID=12345')
  .then(function (response) {
    // 处理成功响应
    console.log(response.data)
  })
  .catch(function (error) {
    // 处理错误
    console.log(error)
  })
  .finally(function () {
    // 总是会执行
  })</code></pre>
      </div>

      <div class="section">
        <h2>Axios API</h2>
        
        <h3>请求方法别名</h3>
        <p>为方便起见，为所有支持的请求方法提供了别名：</p>
        <ul>
          <li><code>axios.request(config)</code></li>
          <li><code>axios.get(url[, config])</code></li>
          <li><code>axios.delete(url[, config])</code></li>
          <li><code>axios.head(url[, config])</code></li>
          <li><code>axios.options(url[, config])</code></li>
          <li><code>axios.post(url[, data[, config]])</code></li>
          <li><code>axios.put(url[, data[, config]])</code></li>
          <li><code>axios.patch(url[, data[, config]])</code></li>
        </ul>
        
        <h3>并发请求</h3>
        <pre><code class="language-javascript">function getUserAccount() {
  return axios.get('/user/12345')
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions')
}

Promise.all([getUserAccount(), getUserPermissions()])
  .then(function (results) {
    const acct = results[0]
    const perm = results[1]
  })</code></pre>
      </div>

      <div class="section">
        <h2>请求配置</h2>
        <p>这些是创建请求时可以用的配置选项。只有 url 是必需的。如果没有指定 method，请求将默认使用 get 方法。</p>
        
        <pre><code class="language-javascript">{
  // `url` 是用于请求的服务器 URL
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // default

  // `baseURL` 将自动加在 `url` 前面，除非 `url` 是一个绝对 URL。
  baseURL: 'https://some-domain.com/api/',

  // `transformRequest` 允许在向服务器发送前，修改请求数据
  transformRequest: [function (data, headers) {
    // 对 data 进行任意转换处理
    return data
  }],

  // `transformResponse` 在传递给 then/catch 前，允许修改响应数据
  transformResponse: [function (data) {
    // 对 data 进行任意转换处理
    return data
  }],

  // `headers` 是即将被发送的自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` 是即将与请求一起发送的 URL 参数
  params: {
    ID: 12345
  },

  // `data` 是作为请求主体被发送的数据
  data: {
    firstName: 'Fred'
  },

  // `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
  timeout: 1000,

  // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // default

  // `adapter` 允许自定义处理请求
  adapter: function (config) {
    /* ... */
  },

  // `auth` 表示应该使用 HTTP 基础验证
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },

  // `responseType` 表示服务器响应的数据类型
  responseType: 'json', // default

  // `responseEncoding` 表示对响应的编码
  responseEncoding: 'utf8', // default

  // `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
  xsrfCookieName: 'XSRF-TOKEN', // default

  // `xsrfHeaderName` 是承载 xsrf token 的值的 HTTP 头的名称
  xsrfHeaderName: 'X-XSRF-TOKEN', // default

  // `onUploadProgress` 允许为上传处理进度事件
  onUploadProgress: function (progressEvent) {
    // Do whatever you want with the native progress event
  },

  // `onDownloadProgress` 允许为下载处理进度事件
  onDownloadProgress: function (progressEvent) {
    // Do whatever you want with the native progress event
  },

  // `maxContentLength` 定义允许的响应内容的最大尺寸
  maxContentLength: 2000,

  // `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise
  validateStatus: function (status) {
    return status >= 200 && status < 300; // default
  },

  // `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
  maxRedirects: 5, // default

  // `socketPath` 定义在 node.js 中使用的 UNIX 套接字
  socketPath: null, // default

  // `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // `proxy` 定义代理服务器的主机名称和端口
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },

  // `cancelToken` 指定用于取消请求的 cancel token
  cancelToken: new CancelToken(function (cancel) {
  })
}</code></pre>
      </div>

      <div class="section">
        <h2>响应结构</h2>
        <p>某个请求的响应包含以下信息：</p>
        
        <pre><code class="language-javascript">{
  // `data` 由服务器提供的响应
  data: {},

  // `status` 来自服务器响应的 HTTP 状态码
  status: 200,

  // `statusText` 来自服务器响应的 HTTP 状态信息
  statusText: 'OK',

  // `headers` 服务器响应的头
  headers: {},

  // `config` 是为请求提供的配置信息
  config: {},

  // `request` 是生成此响应的请求
  request: {}
}</code></pre>
        
        <p>使用 then 时，你将接收下面这样的响应：</p>
        <pre><code class="language-javascript">axios.get('/user/12345')
  .then(function(response) {
    console.log(response.data);
    console.log(response.status);
    console.log(response.statusText);
    console.log(response.headers);
    console.log(response.config);
  });</code></pre>
      </div>

      <div class="section">
        <h2>默认配置</h2>
        <p>你可以指定将被用在各个请求的配置默认值</p>
        
        <h3>全局的 axios 默认值</h3>
        <pre><code class="language-javascript">axios.defaults.baseURL = 'https://api.example.com'
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'</code></pre>
        
        <h3>自定义实例默认值</h3>
        <pre><code class="language-javascript">// 创建实例时设置配置的默认值
const instance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
})

// 在实例已创建后修改默认值
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN</code></pre>
      </div>

      <div class="section">
        <h2>拦截器</h2>
        <p>在请求或响应被 then 或 catch 处理前拦截它们。</p>
        
        <pre><code class="language-javascript">// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error)
  })

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error)
  })</code></pre>
        
        <h3>移除拦截器</h3>
        <pre><code class="language-javascript">const myInterceptor = axios.interceptors.request.use(function () {/*...*/})
axios.interceptors.request.eject(myInterceptor)</code></pre>
        
        <h3>给自定义 axios 实例添加拦截器</h3>
        <pre><code class="language-javascript">const instance = axios.create()
instance.interceptors.request.use(function () {/*...*/})</code></pre>
      </div>

      <div class="section">
        <h2>错误处理</h2>
        <pre><code class="language-javascript">axios.get('/user/12345')
  .catch(function (error) {
    if (error.response) {
      // 请求已发出，但服务器响应的状态码不在 2xx 范围内
      console.log(error.response.data)
      console.log(error.response.status)
      console.log(error.response.headers)
    } else if (error.request) {
      // 请求已发出，但没有收到响应
      console.log(error.request)
    } else {
      // 发送请求时出了点问题
      console.log('Error', error.message)
    }
    console.log(error.config)
  })</code></pre>
        
        <h3>使用 validateStatus 配置选项定义一个自定义 HTTP 状态码的错误范围</h3>
        <pre><code class="language-javascript">axios.get('/user/12345', {
  validateStatus: function (status) {
    return status < 500 // 状态码在 500 以下时 Promise resolve，否则 reject
  }
})</code></pre>
      </div>

      <div class="section">
        <h2>取消请求</h2>
        <p>使用 cancel token 取消请求</p>
        
        <pre><code class="language-javascript">const CancelToken = axios.CancelToken
const source = CancelToken.source()

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message)
  } else {
    // 处理错误
  }
})

// 取消请求（message 参数是可选的）
source.cancel('Operation canceled by the user.')</code></pre>
        
        <p>也可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token：</p>
        <pre><code class="language-javascript">const CancelToken = axios.CancelToken
let cancel

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c
  })
})

// 取消请求
cancel()</code></pre>
      </div>

      <div class="section">
        <h2>在 Vue 3 中使用 Axios</h2>
        
        <h3>基本使用</h3>
        <pre><code class="language-javascript">import { ref, onMounted } from 'vue'
import axios from 'axios'

export default {
  setup() {
    const users = ref([])
    const loading = ref(false)
    const error = ref(null)
    
    const fetchUsers = async () => {
      loading.value = true
      error.value = null
      
      try {
        const response = await axios.get('https://jsonplaceholder.typicode.com/users')
        users.value = response.data
      } catch (err) {
        error.value = err.message || '获取用户数据失败'
      } finally {
        loading.value = false
      }
    }
    
    onMounted(() => {
      fetchUsers()
    })
    
    return {
      users,
      loading,
      error,
      fetchUsers
    }
  }
}</code></pre>
        
        <h3>创建 API 服务</h3>
        <pre><code class="language-javascript">// api/index.js
import axios from 'axios'

// 创建 axios 实例
const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
})

// 请求拦截器
api.interceptors.request.use(
  config => {
    // 在发送请求之前做些什么
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  error => {
    // 对请求错误做些什么
    return Promise.reject(error)
  }
)

// 响应拦截器
api.interceptors.response.use(
  response => {
    // 对响应数据做点什么
    return response
  },
  error => {
    // 对响应错误做点什么
    if (error.response?.status === 401) {
      // 处理未授权错误
      localStorage.removeItem('token')
      window.location.href = '/login'
    }
    return Promise.reject(error)
  }
)

export default api</code></pre>
        
        <pre><code class="language-javascript">// services/userService.js
import api from '../api'

export const userService = {
  // 获取用户列表
  getUsers() {
    return api.get('/users')
  },
  
  // 获取单个用户
  getUser(id) {
    return api.get(`/users/${id}`)
  },
  
  // 创建用户
  createUser(userData) {
    return api.post('/users', userData)
  },
  
  // 更新用户
  updateUser(id, userData) {
    return api.put(`/users/${id}`, userData)
  },
  
  // 删除用户
  deleteUser(id) {
    return api.delete(`/users/${id}`)
  }
}</code></pre>
        
        <pre><code class="language-javascript">// UserList.vue
import { ref, onMounted } from 'vue'
import { userService } from '../services/userService'

export default {
  setup() {
    const users = ref([])
    const loading = ref(false)
    
    const loadUsers = async () => {
      loading.value = true
      try {
        const response = await userService.getUsers()
        users.value = response.data
      } catch (error) {
        console.error('获取用户列表失败:', error)
      } finally {
        loading.value = false
      }
    }
    
    onMounted(() => {
      loadUsers()
    })
    
    return {
      users,
      loading,
      loadUsers
    }
  }
}</code></pre>
      </div>

      <div class="section">
        <h2>文件上传</h2>
        <pre><code class="language-javascript">const uploadFile = async (file) => {
  const formData = new FormData()
  formData.append('file', file)
  
  try {
    const response = await axios.post('/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      },
      onUploadProgress: function (progressEvent) {
        // 处理上传进度
        if (progressEvent.lengthComputable) {
          const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
          console.log(percentCompleted)
        }
      }
    })
    
    console.log('上传成功:', response.data)
  } catch (error) {
    console.error('上传失败:', error)
  }
}</code></pre>
      </div>
    </div>
  </div>
</template>

<script setup>
// 页面逻辑可以在这里添加
</script>

<style scoped>
@import '@/assets/tutorial-styles.css';
</style>